package com.qianda.qdminiapp.common.util;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.AbstractResponseHandler;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * Http client executor
 * 
 * @author gaigeshen
 * @since 12/27 2017
 * 
 * @see org.apache.http.impl.client.CloseableHttpClient
 */
public final class HttpClientExecutor {
  
  private static final Logger LOG = LoggerFactory.getLogger(HttpClientExecutor.class);
  
  private PoolingHttpClientConnectionManager connMgr;
  
  private CloseableHttpClient client;
  private RequestConfig config;
  
  private SSLContext sslContext;
  
  /**
   * Create http client executor
   * 
   * @param connectionRequestTimeout 从连接池中获取连接的超时时间
   * @param connectTimeout 连接服务器的超时时间
   * @param socketTimeout 服务器响应数据的超时时间
   */
  public HttpClientExecutor(
      int connectionRequestTimeout,
      int connectTimeout,
      int socketTimeout) {
    
    this(connectionRequestTimeout, connectTimeout, socketTimeout, null);

  }
  
  /**
   * Create http client executor
   * 
   * @param connectionRequestTimeout 从连接池中获取连接的超时时间
   * @param connectTimeout 连接服务器的超时时间
   * @param socketTimeout 服务器响应数据的超时时间
   * @param sslContext 安全套接字协议
   */
  public HttpClientExecutor(
      int connectionRequestTimeout,
      int connectTimeout,
      int socketTimeout,
      SSLContext sslContext) {
    
    this.config = RequestConfig.custom()
        .setConnectTimeout(connectTimeout)
        .setSocketTimeout(socketTimeout)
        .setConnectionRequestTimeout(connectionRequestTimeout)
        .build();
    
    this.sslContext = sslContext;
    
    initialize();
    
  }
  
  /**
   * 执行请求并返回字符串类型的结果
   * 
   * @param req 请求内容
   * @return 字符串结果
   */
  public String execute(HttpUriRequest req) {
    
    return execute(req, new BasicResponseHandler());
    
  }
  
  /**
   * 执行请求并返回指定类型的结果
   * 
   * @param req 请求内容
   * @param handler 响应处理器
   * @return 结果
   */
  public <T> T execute(HttpUriRequest req, AbstractResponseHandler<T> handler) {
    
    try {
      
      return client.execute(req, handler);
      
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
    
  }
  
  /**
   * 关闭并释放所有的资源
   */
  public void shutdown() {
    LOG.info("Shutdown http client executor");
    try {
      client.close();
    } catch (IOException e) {
      LOG.warn("Shutdown http client failed", e);
    }
  }
  
  /**
   * 创建客户端
   */
  private void initialize() {
    
    LOG.info("Initialize http client executor");
    
    SSLConnectionSocketFactory sslf = null;
    
    if (sslContext != null) {
      sslf = new SSLConnectionSocketFactory(sslContext,
          NoopHostnameVerifier.INSTANCE);
    } else {
      sslf = SSLConnectionSocketFactory.getSocketFactory();
    }
    
    Registry<ConnectionSocketFactory> sfr = RegistryBuilder.<ConnectionSocketFactory>create()
      .register("http", PlainConnectionSocketFactory.getSocketFactory())
      .register("https", sslf)
      .build();
    
    connMgr = new PoolingHttpClientConnectionManager(sfr);
    connMgr.setDefaultMaxPerRoute(100);
    connMgr.setMaxTotal(200);
    connMgr.setValidateAfterInactivity(1000);
    
    client = HttpClients.custom()
      .setConnectionManager(connMgr)
      .evictExpiredConnections()
      .evictIdleConnections(1800, TimeUnit.SECONDS)
      .setDefaultRequestConfig(config)
      .build();
    
    LOG.info("Http client executor initialized");
  }
  
}
